

#import "WXNumberAnimationManager.h"
#import <QuartzCore/QuartzCore.h>

#if !__has_feature(objc_arc)
#error UICountingLabel is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif

#pragma mark - WXNumberAnimationCounter

#ifndef kWXNumberAnimationCounterRate
#define kWXNumberAnimationCounterRate 3.0
#endif

@protocol WXNumberAnimationCounter<NSObject>
-(CGFloat)update:(CGFloat)t;
@end

@interface WXNumberAnimationCounterLinear : NSObject<WXNumberAnimationCounter>

@end

@interface WXNumberAnimationCounterEaseIn : NSObject<WXNumberAnimationCounter>

@end

@interface WXNumberAnimationCounterEaseOut : NSObject<WXNumberAnimationCounter>

@end

@interface WXNumberAnimationCounterEaseInOut : NSObject<WXNumberAnimationCounter>

@end

@interface WXNumberAnimationCounterEaseInBounce : NSObject<WXNumberAnimationCounter>

@end

@interface WXNumberAnimationCounterEaseOutBounce : NSObject<WXNumberAnimationCounter>

@end

@implementation WXNumberAnimationCounterLinear

-(CGFloat)update:(CGFloat)t {
    return t;
}

@end

@implementation WXNumberAnimationCounterEaseIn

-(CGFloat)update:(CGFloat)t {
    return powf(t, kWXNumberAnimationCounterRate);
}

@end

@implementation WXNumberAnimationCounterEaseOut

-(CGFloat)update:(CGFloat)t {
    return 1.0-powf((1.0-t), kWXNumberAnimationCounterRate);
}

@end

@implementation WXNumberAnimationCounterEaseInOut

-(CGFloat)update:(CGFloat)t {
    t *= 2;
    if (t < 1)
        return 0.5f * powf (t, kWXNumberAnimationCounterRate);
    else
        return 0.5f * (2.0f - powf(2.0 - t, kWXNumberAnimationCounterRate));
}

@end

@implementation WXNumberAnimationCounterEaseInBounce

-(CGFloat)update:(CGFloat)t {
    
    if (t < 4.0 / 11.0) {
        return 1.0 - (powf(11.0 / 4.0, 2) * powf(t, 2)) - t;
    }
    
    if (t < 8.0 / 11.0) {
        return 1.0 - (3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2)) - t;
    }
    
    if (t < 10.0 / 11.0) {
        return 1.0 - (15.0 /16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2)) - t;
    }
    
    return 1.0 - (63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2)) - t;
    
}

@end

@implementation WXNumberAnimationCounterEaseOutBounce

-(CGFloat)update:(CGFloat)t {
    
    if (t < 4.0 / 11.0) {
        return powf(11.0 / 4.0, 2) * powf(t, 2);
    }
    
    if (t < 8.0 / 11.0) {
        return 3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2);
    }
    
    if (t < 10.0 / 11.0) {
        return 15.0 /16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2);
    }
    
    return 63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2);
    
}

@end

#pragma mark - UICountingLabel

@interface WXNumberAnimationManager ()

@property (assign, nonatomic) CGFloat fromValue;
@property (assign, nonatomic) CGFloat toValue;
@property (assign, nonatomic) NSTimeInterval progress;
@property (assign, nonatomic) NSTimeInterval lastUpdate;
@property (assign, nonatomic) NSTimeInterval totalTime;
@property (copy,   nonatomic) void (^completionBlock)(BOOL finished);
@property (strong, nonatomic) CADisplayLink *timer;
@property (strong, nonatomic) id<WXNumberAnimationCounter> counter;
@property (copy,   nonatomic) void (^curValueBlock)(CGFloat value);
@end

@implementation WXNumberAnimationManager

-(void)countFromValue:(CGFloat)fromValue
              toValue:(CGFloat)toValue
             duration:(NSTimeInterval)duration
               option:(WXNumberAnimationOption)option
             curValue:(void(^)(CGFloat value))curValue
           completion:(void(^)(BOOL finished))completion {
    
    self.fromValue = fromValue;
    self.toValue = toValue;
    self.completionBlock = completion;
    self.curValueBlock = curValue;
    
    [self.timer invalidate];
    self.timer = nil;
    
    if (duration == 0.0) {
        [self runCurValueBlock];
        [self runCompletionBlock];
        return;
    }
    
    self.progress = 0;
    self.totalTime = duration;
    self.lastUpdate = [NSDate timeIntervalSinceReferenceDate];
    
    switch(option) {
        case WXNumberAnimationOptionLinear:
            self.counter = [[WXNumberAnimationCounterLinear alloc] init];
            break;
        case WXNumberAnimationOptionEaseIn:
            self.counter = [[WXNumberAnimationCounterEaseIn alloc] init];
            break;
        case WXNumberAnimationOptionEaseOut:
            self.counter = [[WXNumberAnimationCounterEaseOut alloc] init];
            break;
        case WXNumberAnimationOptionEaseInOut:
            self.counter = [[WXNumberAnimationCounterEaseInOut alloc] init];
            break;
        case WXNumberAnimationOptionEaseOutBounce:
            self.counter = [[WXNumberAnimationCounterEaseOutBounce alloc] init];
            break;
        case WXNumberAnimationOptionEaseInBounce:
            self.counter = [[WXNumberAnimationCounterEaseInBounce alloc] init];
            break;
    }
    
    [self resume];

}


- (void)updateValue:(NSTimer *)timer {
    
    NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
    self.progress += now - self.lastUpdate;
    self.lastUpdate = now;
    
    [self runCurValueBlock];
    
    if (self.progress >= self.totalTime) {
        [self.timer invalidate];
        self.timer = nil;
        self.progress = self.totalTime;
    }
    
    if (self.progress == self.totalTime) {
        [self runCompletionBlock];
    }
}


- (void)resume {
    
    CADisplayLink *timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateValue:)];
    [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
    self.timer = timer;
}

#pragma mark - 回调
- (void)runCompletionBlock {
    
    if (self.completionBlock) {
        self.completionBlock(YES);
        self.completionBlock = nil;
    }
}

- (void)runCurValueBlock {
    
    if (self.curValueBlock) {
        CGFloat curValue = [self currentValue];
        self.curValueBlock(curValue);
    }
}

- (CGFloat)currentValue {
    
    if (self.progress >= self.totalTime) {
        return self.toValue;
    }
    
    CGFloat percent = self.progress / self.totalTime;
    CGFloat updateVal = [self.counter update:percent];
    return self.fromValue + (updateVal * (self.toValue - self.fromValue));
}

@end
